using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Dida.Tool
{
	/// <summary>
	/// The interface for "Document Object Model" objects.
	/// </summary>
	/// <remarks>
	/// <see cref = "DOMObj"/>
	/// </remarks>
	public interface IDOMObj
	{
		/// <summary>
		/// Gets or sets the name of this object. 
		/// </summary>
		/// <remarks>
		/// When you set the name, the name is validated by several things, 
		/// including the <see cref = "ValidateNameChange"/> handlers. 
		/// If the name you try to set is not valid, an exception will be thrown, 
		/// so as caller you should take into account that setting the name may fail.
		/// </remarks>
		string Name { get; set; }
		bool CanRename { get; }
		
		/// <summary>
		/// This event is fired before the name of the object changes. 
		/// </summary>
		/// <remarks>
		/// <para>
		/// You can connect to this event in order to be able to validate 
		/// a name change, before it's actually applied.
		/// </para>
		/// <para>
		/// When a handler doesn't agree with the name change, it should
		/// throw an exception.
		/// </para>
		/// <para>
		/// It's not possible to change the name to another name. 
		/// </para>
		/// </remarks>
		event NameChangedHandler ValidateNameChange;
		event NameChangedHandler NameChanged;
	}

	/// <summary>
	/// The base class for "Document Object Model" objects.
	/// </summary>
	/// <remarks>
	/// <para>
	/// A DOM object is just an object that's part of a object representation of some data.
	/// This class contains many useful features that make it easier to work with
	/// such a DOM structure, like keeping track of changes to it, and editing it 
	/// from a user interface.
	/// </para>
	/// <para>
	/// A common situation where DOM object are useful is to use them
	/// to represent the data that's being edited through a editing tool.
	/// </para>
	/// <para>
	/// Another common scenario is to use it as the backend while parsing data from
	/// an xml file. You first write all the data you read to a DOM structure, 
	/// and when all the data has been read, you create the final, unmodifiable 
	/// objects from this DOM structure.
	/// </para>
	/// </remarks>
	public abstract class DOMObj : IDOMObj
	{
		string name;
		Dictionary<Type, object> metadata = null;

		/// <summary>
		/// Creates a new DOMObj
		/// </summary>
		/// <remarks>
		/// When this constructor is used, it is assumed that the name
		/// will be set through the <see cref = "Name"/> property, in the constructor
		/// of the derived class.
		/// </remarks>
		protected DOMObj()
		{
			name = "_Unnamed_";
		}

		public DOMObj(string name)
		{
			if (name.Trim() == "")
				throw new Exception("A name should not be empty.");

			this.name = name;
		}

		/// <isoverload cref = "IDOMObj.Name"/>
		public string Name
		{
			get { return name; }
			set
			{
				Debug.Assert(CanRename, "You can't rename this object");

				if (value.Trim() == "")
					throw new Exception("A name shouldnt't be empty.");

				NameChangedArgs eventArgs = null;
				if (ValidateNameChange != null || NameChanged != null)
				{
					eventArgs = new NameChangedArgs();
					eventArgs.oldName = name;
					eventArgs.newName = value;
				}

				if (ValidateNameChange != null)
					ValidateNameChange(this, eventArgs);

				name = value;

				if (NameChanged != null)
					NameChanged(this, eventArgs);
			}
		}

		public virtual bool CanRename
		{
			get { return true; }
		}

		public MetaDataAccessor MetaData
		{
			get { return new MetaDataAccessor(this); }
		}

		public class MetaDataAccessor : IEnumerable<object>
		{
			DOMObj obj;

			internal MetaDataAccessor(DOMObj obj)
			{
				this.obj = obj;
			}

			public object this[Type type]
			{
				get
				{
					object ret;
					if (obj.metadata == null)
						return null;
					else if (obj.metadata.TryGetValue(type, out ret))
						return ret;
					else
						return null;
				}

				set
				{
					if (obj.metadata == null)
						obj.metadata = new Dictionary<Type, object>();

					obj.metadata[type] = value;
				}
			}

			public T Get<T>()
			{
				return (T)this[typeof(T)];
			}

			#region IEnumerable<object> Members

			public IEnumerator<object> GetEnumerator()
			{
				if (obj.metadata != null)
					return obj.metadata.Values.GetEnumerator();
				else
				{
					object[] emptyArr = new object[0];
					return ((IEnumerable<object>)emptyArr).GetEnumerator();
				}
			}

			#endregion

			#region IEnumerable Members

			IEnumerator IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}

			#endregion
		}

		/// <isoverload cref = "IDOMObj.ValidateNameChange"/>
		public event NameChangedHandler ValidateNameChange;
		/// <isoverload cref = "IDOMObj.NameChanged"/>
		public event NameChangedHandler NameChanged;
	}

	public class NameChangedArgs : EventArgs
	{
		/// <summary>
		/// The name the object had before the name change.
		/// </summary>
		public string oldName;
		/// <summary>
		/// The name the object will have after the name change.
		/// </summary>
		public string newName;
	}

	public delegate void NameChangedHandler(object sender,NameChangedArgs args);	
}